﻿#define PREBUILT
#define OLD

using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NeturalMath.Properties;

namespace NeturalMath.Parser
{
    /// <summary>
    /// The Lexer is used to turn raw text input into token strings for the MathProcssor to process
    /// </summary>
    public static class Lexer_old
    {
        #region Constants

        // Disable compiler warning for XML documentation tags for regular expressions
        // as it is overkill to document every regex in detail.
        #pragma warning disable 1591
        
        private static readonly List<TokenDefinition> TokenDefs = new List<TokenDefinition>();

        public static readonly string Comment = @"[\/][\/](.*)";

        public static readonly string VariableUse = @"[a-zA-Z][a-zA-Z0-9]*";
        public static readonly string NumericConstant = @"[+-]?([0-9]+)(\.([0-9]*))?";
        public static readonly string StringConstant = @"\'(.*)\'";
        public static readonly string Assignment = @"(\s*)[=](\s*)";

        public static readonly string VariableAssignment=VariableUse+Assignment;

        public static readonly string GroupBegin = @"[\(]";
        public static readonly string GroupEnd = @"[\)]";
        public static readonly string DomainBegin = @"[\{]";
        public static readonly string DomainEnd = @"[\}]";
        public static readonly string NamedDomainBegin = VariableAssignment + DomainBegin;
        

        public static readonly string ParameterDef = @"((" + VariableUse + ")|(" + VariableUse + Assignment + "))";
        public static readonly string FunctionDefSuffix = @"\((" + ParameterDef + @"(\,)(\s*))*(" + ParameterDef + @")?\)";
        public static readonly string FunctionDefinition = VariableUse + FunctionDefSuffix + Assignment;

        public static readonly string FunctionCallSuffix = @"\(.*\)";
        public static readonly string FunctionCall = VariableUse + FunctionCallSuffix;

        public static readonly string ImportKeyword = @"(\s*)(import)(\s+)";
        public static readonly string PrintKeyword = @"(\s*)(print)(\s+)";
        public static readonly string PrintfKeyword = @"(\s*)(printf)(\s+)";
        public static readonly string TrueKeyword = @"(\s*)(true)(\s*)";
        public static readonly string FalseKeyword = @"(\s*)(false)(\s*)";
        public static readonly string VoidKeyword = @"(\s*)(void)(\s*)";

        public static readonly Regex VariableMatch = new Regex(VariableUse);
        public static readonly Regex FunctionDefMatch = new Regex(VariableUse + FunctionDefSuffix);
        public static readonly Regex FunctionSuffixMatch = new Regex(FunctionDefSuffix);

        #pragma warning restore 1591

        #endregion

        #region Local Members

        private static bool _isCompiled;
        private static Regex _compiledExpression;

        #endregion

        #region Construction Methods

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")]
        static Lexer_old()
        {
            CreateBuiltInTokens();
        }

        private static void CreateBuiltInTokens()
        {
            // Capture and exclude comments
            Add(TokenType.Comment, Comment);
            Add(TokenType.DomainBegin, DomainBegin);
            Add(TokenType.NamedDomainBegin, NamedDomainBegin);
            Add(TokenType.DomainEnd,DomainEnd);

            // Keywords
            Add(TokenType.ImportKeyword, ImportKeyword);
            Add(TokenType.PrintFKeyword, PrintfKeyword); // printf keyword must come before the print keyword
            Add(TokenType.PrintKeyword, PrintKeyword);
            
            Add(TokenType.TrueConstant, TrueKeyword);
            Add(TokenType.FalseConstant, FalseKeyword);
            Add(TokenType.VoidConstant, VoidKeyword);

            // Functions
            Add(TokenType.FunctionAssignment, FunctionDefinition);
            Add(TokenType.FunctionCall, FunctionCall);

            // Check for String Literals
            Add(TokenType.StringConstant, StringConstant);

            // Variables
            Add(TokenType.VariableAssignment, VariableAssignment);
            Add(TokenType.VariableUsage, VariableUse);

            // Groupings
            Add(TokenType.GroupingBegin, GroupBegin);
            Add(TokenType.GroupingEnd, GroupEnd);

            // Constant Definitions
            Add(TokenType.NumericConstant, NumericConstant);

            // Operators
            Add(TokenType.Operator, @"\^");
            Add(TokenType.Operator, @"\*");
            Add(TokenType.Operator, @"\/");
            Add(TokenType.Operator, @"\%");
            Add(TokenType.Operator, @"\+");
            Add(TokenType.Operator, @"\-");

            // Whitespace & Invalid
            // Stuff to Ignore
            Add(TokenType.Whitespace, @"\s*");
            Add(TokenType.Invalid, @"\.*");
        }

        #endregion

        #region Definition Methods

        internal static TokenDefinition Add(TokenType type,string symbol)
        {
            var newTokenDef = new TokenDefinition(type, symbol);

            TokenDefs.Add(newTokenDef);
            _isCompiled = false;
            return newTokenDef;
        }

        //public static void ClearUserDefinitions()
        //{
        //    _tokenDefs.Clear();
        //    CreateBuiltInTokens();
        //    LoadTokenFiles();
        //}

        private static void Compile()
        {
            const string pattern = "({0})|";
            var builder = new StringBuilder();
            foreach (var def in TokenDefs)
            {
                builder.AppendFormat(pattern,def.TokenSymbol);
            }
            builder.Remove(builder.Length - 1, 1);

            _compiledExpression = new Regex(builder.ToString());
            _isCompiled = true;
        }

        private static void CheckCompile()
        {
            if (!_isCompiled)
                Compile();
        }

        #endregion

        #region Lexical Methods

        /// <summary>
        /// Converts raw text to a set of token strings
        /// </summary>
        /// <param name="raw"></param>
        /// <returns></returns>
        public static IEnumerable<TokenString> LexText(string raw)
        {
            var lines = raw.Split('\n');
            foreach (var line in lines)
            {
                if (!string.IsNullOrWhiteSpace(line))
                    yield return LexLineAndValidate(line);
            }
        }

        /// <summary>
        /// Reads a file and converts it into a set of token strings
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public static IEnumerable<TokenString> LexFile(string filePath)
        {
            var fileContent = File.ReadAllLines(filePath);
            foreach (var line in fileContent)
            {
                if (!string.IsNullOrWhiteSpace(line))
                    yield return LexLineAndValidate(line);
            }
        }

        /// <summary>
        /// Converts a single line of text into a token string and validates the accuracy
        /// </summary>
        /// <param name="raw"></param>
        /// <returns></returns>
        public static TokenString LexLineAndValidate(string raw)
        {
            var tokens = LexLine(raw);
            var badToken = tokens.FirstOrDefault(t => t.Type == TokenType.Invalid);
            if (badToken!=null)
                throw new LexicalException(ErrorCodes.UnrecognizedCharacterSequence, string.Format(Resources.SyntaxUnrecognizedCharacter, badToken.Text));

            var whitespace = tokens.Where(t => t.Type == TokenType.Whitespace).ToArray();
            foreach (var t in whitespace)
                tokens.Remove(t);

            return tokens;
        }

        /// <summary>
        /// Converts a single line of text into a token string
        /// </summary>
        /// <returns></returns>
        public static TokenString LexLine(string raw)
        {
            CheckCompile();
            var matches = _compiledExpression.Matches(raw);
            var tokens = new TokenString();

            foreach (Match match in matches)
            {

                foreach (Capture capture in match.Captures)
                {
                    var value = capture.Value;
                    var def = TokenDefs.First(d => d.IsMatch(value));
                    tokens.AddLast(new Token(def.Type, value));
                    break;
                }
            }
            return tokens;
        }

        #endregion
    }
}
